package com.chen.common.model;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * @author chenzy
 * @since 2021-12-16
 * 对{@link Optional}的扩展，因为Optional为filnally无法继承
 **/
public final class OP<T> {
    private static final OP<?> EMPTY = new OP<>(null);
    private final T value;

    @SuppressWarnings("unchecked")
    public static <T> OP<T> empty() {
        return (OP<T>) EMPTY;
    }

    private OP(T value) {
        this.value = value;
    }

    /**
     * 和{@link Optional#of(Object)}的区别在于，value为null时不会报错，和{@link .#ofNullable(Object)}等价
     */
    public static <T> OP<T> of(T value) {
        return new OP<>(value);
    }

    @SuppressWarnings("unchecked")
    public static <T> OP<T> ofNullable(T value) {
        return value == null ? (OP<T>) EMPTY
                : new OP<>(value);
    }
    /**
     * 没有值时抛异常NoSuchElementException
     */
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    /**
     * 有值返回true
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * 值为空返回true
     */
    public boolean isEmpty() {
        return value == null;
    }

    /**
     * 有值时执行action
     */
    public void ifPresent(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
    }

    /**
     * 有值执行action，没有值执行emptyAction
     */
    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
        if (value != null) {
            action.accept(value);
        } else {
            emptyAction.run();
        }
    }
    public OP<T> filter(Predicate<? super T> predicate) {
        return value == null || predicate.test(value) ? this : empty();
    }
    /**
     * 值不为空时执行，返回自身
     */
    public OP<T> ifDo(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
        return this;
    }
    public OP<T> ifDo(Predicate<? super T> filter,Consumer<? super T> action){
        if (value==null){
            return this;
        }
        if (filter.test(value)){
            action.accept(value);
        }
        return this;
    }
    public <U> OP<U> map(Function<? super T, ? extends U> mapper) {
        return value == null ? empty() : OP.ofNullable(mapper.apply(value));
    }

    public <U> OP<U> flatMap(Function<? super T, OP<U>> mapper) {
        if (value==null){
            return empty();
        }
        var r=mapper.apply(value);
        return r == null ? empty() :  r;
    }
    /**
     *
     */
    public OP<T> flatOr(Supplier<OP<T>> supplier) {
        if (value!=null){
            return this;
        }
        var r =  supplier.get();
        return r == null ? empty() : r;
    }
    public OP<T> or(Supplier<? extends T> supplier) {
        if (value!=null){
            return this;
        }
        return OP.ofNullable(supplier.get());
    }

    /**
     *
     */
    public Stream<T> stream() {
        return value == null ? Stream.empty() : Stream.of(value);
    }
    /**
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    /**
     */
    public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();
    }

    /**
     */
    public T orElseThrow() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    /**
     */
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value==null){
            throw exceptionSupplier.get();
        }
        return value;
    }

    /**
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        return obj instanceof OP<?> other
                && Objects.equals(value, other.value);
    }

    /**
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    /**
     */
    @Override
    public String toString() {
        return value == null?null:value.toString();
    }
}
